﻿// //   Copyright 2007-2011 Comdiv (F. Sadykov) - http://code.google.com/u/fagim.sadykov/
// //   Supported by Media Technology LTD 
// //    
// //   Licensed under the Apache License, Version 2.0 (the "License");
// //   you may not use this file except in compliance with the License.
// //   You may obtain a copy of the License at
// //    
// //        http://www.apache.org/licenses/LICENSE-2.0
// //    
// //   Unless required by applicable law or agreed to in writing, software
// //   distributed under the License is distributed on an "AS IS" BASIS,
// //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// //   See the License for the specific language governing permissions and
// //   limitations under the License.
// //   
// //   MODIFICATIONS HAVE BEEN MADE TO THIS FILE

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Comdiv.QWeb.Binding;
using Comdiv.QWeb.Diagnostics;
using Comdiv.QWeb.Files;
using Comdiv.QWeb.Renders;
using Comdiv.QWeb.Utils;

namespace Comdiv.QWeb.Factory {
	public sealed class QWebFactory : IQWebFactory, IRegistryBound {
		private static bool _qweblibrariesloaded;

		private readonly IDictionary<string, Stack<ActionDescriptor>> _executorcache =
			new Dictionary<string, Stack<ActionDescriptor>>();

		private readonly IDictionary<string, Stack<RenderDescriptor>> _rendercache =
			new Dictionary<string, Stack<RenderDescriptor>>();


		private readonly IDictionary<string, Type> _resolveTypeCache = new Dictionary<string, Type>();
		private readonly ActionDescriptorCreator _actionDescriptorCreator;
		private readonly IList<string> _excludes = new List<string>();
		private readonly RenderDescriptorCreator _renderDescriptorCreator;
		private IDictionary<Type, Stack<IActionBinder>> _bindercache;

		private IActionBinder[] _defaultBinders;
		private bool _prepareAllProcessed;
		private readonly IDictionary<Type, ActionTypeDescriptor> _typedescriptors = new Dictionary<Type, ActionTypeDescriptor>();
		private ITypeLocator _locator;
		private QWebServiceRegistry _registry;

		public QWebFactory() {
			_stats = new QWebStatistics();
			_locator = new stubTypeLocator();
			_actionDescriptorCreator = new ActionDescriptorCreator();
			_renderDescriptorCreator = new RenderDescriptorCreator();
			_registry = new QWebServiceRegistry();
		}

		private IQWebStatistics _stats;

		#region IQWebFactory Members

		public ActionDescriptor GetAction(QWebContext context) {
			var result = context.ActionDescriptor = GetAction(context.ActionName);
			return result;
		}

		public void ReleaseAction(QWebContext context) {
			ReleaseExecutor(context.ActionName, context.ActionDescriptor);
		}

		public RenderDescriptor GetRender(QWebContext context) {
			return context.RenderDescriptor = GetRender(context.RenderType);
		}

		public void ReleaseRender(QWebContext context) {
			ReleaseRender(context.RenderType, context.RenderDescriptor);
			_stats.GetRenderStats(context.RenderType).PoolSize++;
		}


		public IEnumerable<IActionBinder> GetBinders() {
			lock (this) {
				if (null == _bindercache) {
					_bindercache = new Dictionary<Type, Stack<IActionBinder>>();
					var defaultbinder = Registry.Get<IActionBinder>();
					_bindercache[defaultbinder.GetType()] = new Stack<IActionBinder>();
					foreach (var bindtype in Registry.TypeLocator.GetAll<IActionBinder>()) {
						_bindercache[bindtype] = new Stack<IActionBinder>();
					}
				}
				return _defaultBinders ?? (_defaultBinders = _bindercache.Select(x => x.Key.create<IActionBinder>()).ToArray());
			}
		}

		public IActionBinder GetBinder(Type binderType) {
			lock (_bindercache[binderType]) {
				var stack = _bindercache[binderType];
				if (stack.Count > 0) {
					return stack.Pop();
				}
				return binderType.create<IActionBinder>();
			}
		}

		public void ReleaseBinder(IActionBinder binder) {
			lock (_bindercache[binder.GetType()]) {
				_bindercache[binder.GetType()].Push(binder);
			}
		}


		public void RegisterAction<T>(string actionname) where T : IAction {
			var type = typeof (T);
			var fullname = string.IsNullOrEmpty(actionname) ? ActionAttribute.GetName(type) : actionname;
			if (!fullname.EndsWith(".action")) fullname += ".action";
			_resolveTypeCache[fullname] = type;
		}

		public void PrepareAllActionsAndRenders() {
			if (_prepareAllProcessed) return;

			CreateFromDomain<IRender>(null);
			CreateFromDomain<IAction>(null);

			foreach (var type in _resolveTypeCache.Values) {
				if (typeof (IAction).IsAssignableFrom(type)) {
					if (!_typedescriptors.ContainsKey(type)) {
						_typedescriptors[type] = new ActionTypeDescriptor(type) {Factory = this};
					}
				}
			}

			_prepareAllProcessed = true;
		}

		public Type[] GetAllActionTypes() {
			PrepareAllActionsAndRenders();
			return _resolveTypeCache.Values.Where(x => typeof (IAction).IsAssignableFrom(x)).ToArray();
		}

		public Type[] GetAllRenderTypes() {
			PrepareAllActionsAndRenders();
			return _resolveTypeCache.Values.Where(x => typeof (IRender).IsAssignableFrom(x)).ToArray();
		}

		public ActionDescriptor[] GetAllActions() {
			PrepareAllActionsAndRenders();
			return (from item in _resolveTypeCache.ToArray() where typeof (IAction).IsAssignableFrom(item.Value) select GetAction(item.Key)).ToArray();
		}

		public RenderDescriptor[] GetAllRenders() {
			PrepareAllActionsAndRenders();
			return (from item in _resolveTypeCache.ToArray() where typeof (IRender).IsAssignableFrom(item.Value) select GetRender(item.Key)).ToArray();
		}

		public void ReleaseExecutor(string name, ActionDescriptor action) {
			lock (this) {
				if (null == action) return;

				if (!_executorcache.ContainsKey(name)) _executorcache[name] = new Stack<ActionDescriptor>();
				_executorcache[name].Push(action);
				_stats.GetActionStats(name).PoolSize++;
			}
		}

		#endregion

		#region IRegistryBound Members

		public void SetRegistry(QWebServiceRegistry registry) {
			_stats = registry.Statistics;
			_locator = registry.TypeLocator;
			_registry = registry;
		}

		public QWebServiceRegistry Registry {
			get { return _registry; }
		}

		#endregion

		public ActionDescriptor GetAction(string actionname) {
			lock (this) {
				var fullname = actionname;
				if (!fullname.EndsWith(".action")) fullname += ".action";
				if (actionname.EndsWith(".action")) {
					actionname = actionname.Substring(0, actionname.Length - 7);
				}
				ActionDescriptor result = null;
				var st = _stats.GetActionStats(actionname);
				st.Activations++;
				if (!_executorcache.ContainsKey(actionname)) _executorcache[actionname] = new Stack<ActionDescriptor>();
				if (_executorcache[actionname].Count > 0) {
					result = _executorcache[actionname].Pop();
					st.PoolSize--;
				}
				if (null == result) {
					if (_resolveTypeCache.ContainsKey(fullname) && _resolveTypeCache[fullname] != null) {
						result = _actionDescriptorCreator.CreateDescriptor(null, _resolveTypeCache[fullname].create<IAction>());
					}
					else {
						var t = _locator.Get<IAction>(actionname.EndsWith(".qweb.action") ? actionname : actionname + ".qweb.action");
						if (null != t) {
							result = _actionDescriptorCreator.CreateDescriptor(null, t.create<IAction>());
						}
						if (null == result) {
							result = _actionDescriptorCreator.CreateDescriptor(null, DefaultFindAction(actionname));
						}
						_resolveTypeCache[fullname] = null == result ? null : result.Action.GetType();
						if (null != result) {
							st.Type = result.Action.GetType().FullName;
						}
					}


					if (null == result) {
						throw new ActionNotFoundException(actionname);
					}

					var registryBound = result.Action as IRegistryBound;
					if (registryBound != null) {
						(registryBound).SetRegistry(Registry);
					}

					st.Creations++;
				}
				if (null == result.TypeDescriptor) {
					if (!_typedescriptors.ContainsKey(result.Action.GetType())) {
						_typedescriptors[result.Action.GetType()] = new ActionTypeDescriptor(result.Action.GetType()) {Factory = this};
					}

					result.TypeDescriptor = _typedescriptors[result.Action.GetType()];
				}
				return result;
			}
		}


		private IAction DefaultFindAction(string name) {
			if (_excludes.Contains("action." + name)) return null;
			var result = CreateFromDomain<IAction>(name);
			if (null != result) return result;

			_excludes.Add("action." + name);
			return null;
		}

		private RenderDescriptor GetRender(string rendername) {
			lock (this) {
				var st = _stats.GetRenderStats(rendername);
				st.Activations++;
				var fullname = rendername;
				if (!fullname.EndsWith(".render")) fullname += ".render";
				RenderDescriptor result = null;
				if (!_rendercache.ContainsKey(rendername)) _rendercache[rendername] = new Stack<RenderDescriptor>();

				if (_rendercache[rendername].Count > 0) {
					result = _rendercache[rendername].Pop();
					st.PoolSize--;
				}
				if (null == result) {
					if (_resolveTypeCache.ContainsKey(fullname)) {
						if (_resolveTypeCache[fullname] != null) {
							result = _renderDescriptorCreator.CreateDescriptor(null, _resolveTypeCache[fullname].create<IRender>());
						}
					}
					else {
						result = _renderDescriptorCreator.CreateDescriptor(null, GetStandardRender(rendername));
						if (null == result) {
							var t = _locator.Get<IRender>(rendername.EndsWith(".qweb.render") ? rendername : rendername + ".qweb.render");
							if (null != t) result = _renderDescriptorCreator.CreateDescriptor(null, t.create<IRender>());
						}
						if (null == result) {
							result = _renderDescriptorCreator.CreateDescriptor(null, DefaultFindRender(rendername));
						}


						_resolveTypeCache[fullname] = null == result ? null : result.Render.GetType();
						if (null != result) {
							st.Type = result.Render.GetType().FullName;
						}
					}
					if (null == result) {
						throw new RenderNotFoundException(rendername);
					}
					var registryBound = result.Render as IRegistryBound;
					if (registryBound != null) {
						(registryBound).SetRegistry(_registry);
					}
					st.Creations++;
				}

				return result;
			}
		}

		private IRender DefaultFindRender(string name) {
			if (_excludes.Contains("render." + name)) return null;
			var result = CreateFromDomain<IRender>(name);
			if (null != result) return result;
			_excludes.Add("render." + name);
			return null;
		}

		private T CreateFromDomain<T>(string name) {
			EnsureQwebLibsLoaded();
			var assemblies =
				AppDomain.CurrentDomain.GetAssemblies();
#if DEBUG_LOG
			registry.FileNameResolver.Write("~/tmp/resolve_over_domain.log", "find for " + name + " " + DateTime.Now + "\r\n",append: true);
#endif
			Type found = null;
			foreach (var a in assemblies) {
				var aname = a.GetName().Name;
				if (aname == "System") continue;
				if (aname.StartsWith("System.")) continue;
				if (aname.StartsWith("Microsoft.")) continue;
				if (aname == "mscorlib") continue;
				if (aname == "CppCodeProvider") continue;

				try {
#if DEBUG_LOG
					registry.FileNameResolver.Write("~/tmp/resolve_over_domain.log", "try DLL " + aname + "\r\n", append: true);	
#endif
					var types = a.GetTypes().Where(x => typeof (T).IsAssignableFrom(x) && !x.IsAbstract);

					foreach (var type in types) {
#if DEBUG_LOG
						registry.FileNameResolver.Write("~/tmp/resolve_over_domain.log", "try Type  " + type.FullName + "\r\n", append: true);	
#endif


						var typename = "";
						if (typeof (T) == typeof (IRender)) {
							typename = RenderAttribute.GetName(type).ToLower();
							_resolveTypeCache[typename + ".render"] = type;
						}

						if (typeof (T) == typeof (IAction)) {
							typename = ActionAttribute.GetName(type).ToLower();
							_resolveTypeCache[typename + ".action"] = type;
						}
						if (!string.IsNullOrWhiteSpace(name)) {
							var n = name.ToLower();
							if (n.EndsWith(".render") || n.EndsWith(".action")) {
								n = n.Substring(0, 7);
							}
							else if (n.EndsWith(".quick")) {
								n = n.Substring(0, 6);
							}
							if (n == typename.ToLower()) {
								found = type;
							}
						}
					}
				}
				catch (ReflectionTypeLoadException) {
				}
			}
			return null != found ? found.create<T>() : default(T);
		}

		private void EnsureQwebLibsLoaded() {
			if (_qweblibrariesloaded) return;

			var defaultbins =
				_registry.FileNameResolver
					.ResolveAll(QWebContext.Current,
					            FileResolveResultType.FullPath,
					            FileNameResolverExtensions.DEFAULT_BIN_RESOLVE_PROBE_PATHS,
					            new[] {"*qweb*.dll", "Comdiv*.dll"});

			foreach (var qweblib in defaultbins) {
				Assembly.LoadFrom(qweblib);
			}

			_qweblibrariesloaded = true;
		}

		private static IRender GetStandardRender(string type) {
			if ("js" == type) return new JsRender();
			if ("json" == type) return new JsonRender();
			if ("xml" == type) return new XmlRender();
			if ("view" == type) return new ViewRender();
			if ("bxl" == type) return new BxlRender();
			if ("md5" == type) return new Md5Render();
			if ("string" == type) return new StringRender();
			if ("file" == type) return new FileRender();
			if ("qview" == type) return new QViewRender();
			if ("form" == type) return new FormRender();
			if ("embedjs" == type) return new EmbedJsRender();
			if ("embedjson" == type) return new EmbedJsonRender();
			return null;
		}

		private void ReleaseRender(string type, RenderDescriptor render) {
			lock (this) {
				if (null == render) return;

				if (!_rendercache.ContainsKey(type)) _rendercache[type] = new Stack<RenderDescriptor>();
				_rendercache[type].Push(render);
			}
		}
	}
}